package org.gjy.m8.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author gjy
 * @version 1.0
 * @since 2025-11-10 15:49:13
 */
public class ZipExtractor {

    private static final int BUFFER_SIZE = 8192;
    private static final DateTimeFormatter DATE_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 解压 ZIP 文件到指定目录
     */
    public static void extract(String zipFilePath, String targetDir) throws IOException {
        extract(zipFilePath, targetDir, null);
    }

    /**
     * 解压 ZIP 文件到指定目录（带进度回调）
     */
    public static void extract(String zipFilePath, String targetDir,
                               ProgressCallback callback) throws IOException {
        extract(Paths.get(zipFilePath), Paths.get(targetDir), callback);
    }

    /**
     * 解压 ZIP 文件到指定目录
     */
    public static void extract(Path zipFile, Path targetDir,
                               ProgressCallback callback) throws IOException {
        if (!Files.exists(zipFile) || !Files.isRegularFile(zipFile)) {
            throw new IllegalArgumentException("ZIP 文件不存在或不是有效文件: " + zipFile);
        }

        // 创建目标目录
        if (!Files.exists(targetDir)) {
            Files.createDirectories(targetDir);
        }

        long totalEntries = countZipEntries(zipFile);
        long processedEntries = 0;

        try (ZipFile zip = new ZipFile(zipFile.toFile(), StandardCharsets.UTF_8)) {
            Enumeration<? extends ZipEntry> entries = zip.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                Path targetFile = targetDir.resolve(entry.getName());

                if (entry.isDirectory()) {
                    // 创建目录
                    if (!Files.exists(targetFile)) {
                        Files.createDirectories(targetFile);
                    }
                } else {
                    // 确保父目录存在
                    Path parentDir = targetFile.getParent();
                    if (parentDir != null && !Files.exists(parentDir)) {
                        Files.createDirectories(parentDir);
                    }

                    // 复制文件
                    try (InputStream is = zip.getInputStream(entry);
                         OutputStream os = Files.newOutputStream(targetFile.toFile().toPath())) {
                        copyStream(is, os);
                    }

                    long fileSize = entry.getSize();
                    logInfo("已解压: %s (%.2f KB)", entry.getName(), fileSize / 1024.0);
                }

                processedEntries++;

                if (callback != null) {
                    int progress = (int) (processedEntries * 100 / totalEntries);
                    callback.onProgress(progress, processedEntries, totalEntries);
                }
            }
        }

        logInfo("ZIP 解压完成: %s -> %s", zipFile, targetDir);
    }

    /**
     * 解压 ZIP 文件到当前目录
     */
    public static void extractToCurrentDir(String zipFilePath) throws IOException {
        extractToCurrentDir(Paths.get(zipFilePath), null);
    }

    /**
     * 解压 ZIP 文件到当前目录（带进度回调）
     */
    public static void extractToCurrentDir(String zipFilePath,
                                           ProgressCallback callback) throws IOException {
        extractToCurrentDir(Paths.get(zipFilePath), callback);
    }

    /**
     * 解压 ZIP 文件到当前目录
     */
    public static void extractToCurrentDir(Path zipFile,
                                           ProgressCallback callback) throws IOException {
        Path currentDir = Paths.get(System.getProperty("user.dir"));
        extract(zipFile, currentDir, callback);
    }

    /**
     * 获取 ZIP 文件信息
     */
    public static List<FileInfo> getZipFileInfo(String zipFilePath) throws IOException {
        return getZipFileInfo(Paths.get(zipFilePath));
    }

    /**
     * 获取 ZIP 文件信息
     */
    public static List<FileInfo> getZipFileInfo(Path zipFile) throws IOException {
        List<FileInfo> fileInfoList = new ArrayList<>();

        try (ZipFile zip = new ZipFile(zipFile.toFile(), StandardCharsets.UTF_8)) {
            Enumeration<? extends ZipEntry> entries = zip.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                fileInfoList.add(new FileInfo(
                        entry.getName(),
                        entry.isDirectory(),
                        entry.getSize(),
                        entry.getCompressedSize(),
                        entry.getTime(),
                        entry.getMethod()
                ));
            }
        }

        return fileInfoList;
    }

    /**
     * 检查 ZIP 文件是否完整
     */
    public static boolean isValidZipFile(String zipFilePath) {
        return isValidZipFile(Paths.get(zipFilePath));
    }

    /**
     * 检查 ZIP 文件是否完整
     */
    public static boolean isValidZipFile(Path zipFile) {
        try (ZipFile zip = new ZipFile(zipFile.toFile(), StandardCharsets.UTF_8)) {
            // 如果能成功打开并且没有异常，则认为 ZIP 文件有效
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    // ========== 辅助方法 ==========

    private static long countZipEntries(Path zipFile) throws IOException {
        try (ZipFile zip = new ZipFile(zipFile.toFile(), StandardCharsets.UTF_8)) {
            return zip.size();
        }
    }

    private static void copyStream(InputStream is, OutputStream os) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;

        while ((bytesRead = is.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
    }

    private static void logInfo(String format, Object... args) {
        System.out.printf("[%s] INFO: %s%n",
                LocalDateTime.now().format(DATE_FORMATTER),
                String.format(format, args)
        );
    }

}
